using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using Toenda.Foundation.Utility;

namespace Toenda.Foundation.Data {
	/// <summary>
	/// Implements a string-query for lists
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class ListQuery<T> {
		private List<T> _internalCache = new List<T>();
		private List<T> _result = new List<T>();
		private List<string> _keyWords = new List<string>();
		private BindingFlags _flags;

		/// <summary>
		/// Creates a new instance of <see cref="QueryParser"/>
		/// </summary>
		public ListQuery(List<T> cache) {
			this._internalCache = cache;
			this._flags = BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

			//this._keyWords.AddRange(
			//    new string[] { 
			//        "from", "where", "select", "group", "into", 
			//        "orderby", "join", "let", "in", "on",
			//        "equals", "by", "ascending", "descending", "like",
			//        "<", ">", "==", ">=", "<=", "!="
			//    }
			//);

			this._keyWords.AddRange(
				new string[] { 
					"orderby", "in", "equals", "ascending", "descending", 
					"like", "asc", "desc",
					"<", ">", "==", "=", ">=", "<=", "!="
				}
			);
		}

		/// <summary>
		/// Gets the internal list of key words
		/// </summary>
		public List<string> KeyWords {
			get { return this._keyWords; }
		}

		/// <summary>
		/// Gets the results
		/// </summary>
		public List<T> Result {
			get { return this._result; }
		}

		/// <summary>
		/// Executes the query
		/// </summary>
		/// <param name="query"></param>
		/// <returns></returns>
		public List<T> ExecuteQuery(string query) {
			List<string> tmpSplitingList = new List<string>(query.Split(" "));

			List<string> list = new List<string>();
			string singleQuery = "";

			foreach(string part in tmpSplitingList) {
				if(part.Equals("and")
				|| part.Equals("AND")
				|| part.Equals("or")
				|| part.Equals("OR")) {
					list.Add(singleQuery);
					singleQuery = part + " ";
				}
				else {
					singleQuery += part + " ";
				}
			}

			list.Add(singleQuery);

			//List<string> list = new List<string>(
			//    query.Split(new string[] { " and ", " or ", " AND ", " OR " }, StringSplitOptions.RemoveEmptyEntries)
			//);

			List<string[]> queryList = new List<string[]>();

			foreach(string q in list) {
				string tmpQ = q.Trim();

				Console.WriteLine(tmpQ);
				List<string> subList = this.SplitQuery(tmpQ);

				//List<string> subList = new List<string>(
				//    tmpQ.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
				//);

				// execute
				if(subList.Count == 3) {
					Console.WriteLine("      -> execute \"{0}\"", tmpQ);

					string propertyName = subList[0].Replace("[", "").Replace("]", "").Trim();
					string operation = subList[1].Trim();
					string value = subList[2].Replace("'", "").Trim();

					queryList.Add(new string[] { propertyName, operation, value });

					// test print
					foreach(string s in subList) {
						if(this._keyWords.Contains(s)
						&& !s.StartsWith("[")
						&& !s.EndsWith("]")) {
							ConsoleColor cc = Console.ForegroundColor;
							Console.ForegroundColor = ConsoleColor.Red;
							Console.WriteLine("   " + s);
							Console.ForegroundColor = cc;
						}
						else {
							Console.WriteLine("   " + s);
						}
					}
				}
				else if(subList.Count == 4) {
					Console.WriteLine("      -> execute \"{0}\"", tmpQ);

					string propertyName = subList[0] + " " + subList[1].Replace("[", "").Replace("]", "").Trim();
					string operation = subList[2].Trim();
					string value = subList[3].Replace("'", "").Trim();

					queryList.Add(new string[] { propertyName, operation, value });

					// test print
					foreach(string s in subList) {
						if(s.Equals("and")
						|| s.Equals("AND")
						|| s.Equals("or")
						|| s.Equals("OR")) {
							// ---
						}
						else {
							if(this._keyWords.Contains(s)
							&& !s.StartsWith("[")
							&& !s.EndsWith("]")) {
								ConsoleColor cc = Console.ForegroundColor;
								Console.ForegroundColor = ConsoleColor.Red;
								Console.WriteLine("   " + s);
								Console.ForegroundColor = cc;
							}
							else {
								Console.WriteLine("   " + s);
							}
						}
					}
				}
				else {
					Console.WriteLine(
						"      -> Incorrect sub query \"{0}\", step ignored!",
						tmpQ
					);
				}
			}

			List<T> tmpList = this.FindBySubQuery(queryList);

			foreach(T cc in tmpList) {
				if(!this._result.Contains(cc)) {
					this._result.Add(cc);
				}
			}

			return this._result;
		}

		private List<T> FindBySubQuery(List<string[]> queryList) {
			List<T> result = new List<T>();

			foreach(T cc in this._internalCache) {
				object obj = (object)cc;
				bool canAdd = true;

				bool searchOR = false;

				foreach(string[] query in queryList) {
					string propertyName = query[0];

					if(propertyName.ToLower().StartsWith("or ")) {
						searchOR = true;
					}
					else if(propertyName.ToLower().StartsWith("and ")) {
						searchOR = false;
					}
					else {
						searchOR = false;
					}
				}

				foreach(string[] query in queryList) {
					string propertyName = query[0];
					string operation = query[1];
					string value = query[2];

					if(propertyName.ToLower().StartsWith("or ")) {
						//searchOR = true;
						propertyName = propertyName.Substring(3);
					}
					else if(propertyName.ToLower().StartsWith("and ")) {
						//searchOR = false;
						propertyName = propertyName.Substring(4);
					}
					else {
						//searchOR = false;
					}

					foreach(PropertyInfo pi in obj.GetType().GetProperties(this._flags)) {
						try {
							if(pi.CanRead) {
								string checkPropertyName = propertyName;

								if(propertyName.Contains(".")) {
									checkPropertyName = propertyName.Substring(0, propertyName.IndexOf("."));
								}

								if(pi.Name == checkPropertyName) {
									// possible operations:
									// in, equals, like, <, >, ==, >=, <=, !=

									Type objType = pi.PropertyType;
									object objValue = pi.GetValue(obj, null);
									string tmp = "";

									switch(operation) {
										case "like":
											if(value.StartsWith("%") 
											&& value.EndsWith("%") 
											&& (canAdd || searchOR)) {
												tmp = value.Replace("%", "");

												if(objValue.ToString().Contains(tmp)) {
													if(searchOR) {
														if(!result.Contains(cc)) {
															result.Add(cc);
														}
													}
													else {
														canAdd = true;
													}
												}
												else {
													canAdd = false;
												}
											}
											else if(value.StartsWith("%")) {
												tmp = value.Replace("%", "");

												if(objValue.ToString().EndsWith(tmp)
												&& (canAdd || searchOR)) {
													if(searchOR) {
														if(!result.Contains(cc)) {
															result.Add(cc);
														}
													}
													else {
														canAdd = true;
													}
												}
												else {
													canAdd = false;
												}
											}
											else if(value.EndsWith("%")) {
												tmp = value.Replace("%", "");

												if(objValue.ToString().StartsWith(tmp)
												&& (canAdd || searchOR)) {
													if(searchOR) {
														if(!result.Contains(cc)) {
															result.Add(cc);
														}
													}
													else {
														canAdd = true;
													}
												}
												else {
													canAdd = false;
												}
											}
											break;

										case "<":
										case ">":
										case "==":
										case "=":
										case ">=":
										case "<=":
										case "!=":
											if(Operate(operation, objValue, (object)value, propertyName)
											&& (canAdd || searchOR)) {
												if(searchOR) {
													if(!result.Contains(cc)) {
														result.Add(cc);
													}
												}
												else {
													canAdd = true;
												}
											}
											else {
												canAdd = false;
											}
											break;

										case "equals":
											//if(objValue.Equals((object)namespace);
											canAdd = false;
											break;

										case "in":
										default:
											canAdd = false;
											break;
									}
								}
							}
						}
						catch(Exception) {
						}
					}
				}

				if(!searchOR) {
					if(canAdd && !result.Contains(cc)) {
						result.Add(cc);
					}
				}
			}

			return result;
		}

		private bool Operate(string operation, object checkObj, object accordingObj, string propertyName) {
			try {
				switch(operation) {
					case "<":
						if(checkObj is short) {
							return (Int16)checkObj < StringExtensions.ToInt16((string)accordingObj);
						}
						else if(checkObj is int) {
							return (Int32)checkObj < StringExtensions.ToInt32((string)accordingObj);
						}
						else if(checkObj is long) {
							return (Int64)checkObj < StringExtensions.ToInt64((string)accordingObj);
						}
						else if(checkObj is DateTime) {
							return (DateTime)checkObj < StringExtensions.ToDateTime((string)accordingObj);
						}
						else if(checkObj is double) {
							return (double)checkObj < StringExtensions.ToDouble((string)accordingObj);
						}
						else if(checkObj is decimal) {
							return (decimal)checkObj < StringExtensions.ToDecimal((string)accordingObj);
						}
						else if(checkObj is float) {
							return (float)checkObj < StringExtensions.ToFloat((string)accordingObj);
						}
						else if(checkObj is System.Collections.IList) {
							/*
							 * try to run trough a list of items
							 * */
							foreach(var item in (System.Collections.IList)checkObj) {
								foreach(PropertyInfo pi in item.GetType().GetProperties(this._flags)) {
									try {
										if(pi.CanRead) {
											string checkPropertyName = propertyName;

											if(propertyName.Contains(".")) {
												checkPropertyName = propertyName.Substring(propertyName.LastIndexOf(".") + 1);
											}

											if(pi.Name == checkPropertyName) {
												object objValue = pi.GetValue(item, null);

												if(objValue.ToString().ToDouble() == accordingObj.ToString().ToDouble()) {
													return true;
												}
											}
										}
									}
									catch {
									}
								}
							}

							return false;
						}
						break;

					case ">":
						if(checkObj is short) {
							return (Int16)checkObj > StringExtensions.ToInt16((string)accordingObj);
						}
						else if(checkObj is int) {
							return (Int32)checkObj > StringExtensions.ToInt32((string)accordingObj);
						}
						else if(checkObj is long) {
							return (Int64)checkObj > StringExtensions.ToInt64((string)accordingObj);
						}
						else if(checkObj is DateTime) {
							return (DateTime)checkObj > StringExtensions.ToDateTime((string)accordingObj);
						}
						else if(checkObj is double) {
							return (double)checkObj > StringExtensions.ToDouble((string)accordingObj);
						}
						else if(checkObj is decimal) {
							return (decimal)checkObj > StringExtensions.ToDecimal((string)accordingObj);
						}
						else if(checkObj is float) {
							return (float)checkObj > StringExtensions.ToFloat((string)accordingObj);
						}
						else if(checkObj is System.Collections.IList) {
							/*
							 * try to run trough a list of items
							 * */
							foreach(var item in (System.Collections.IList)checkObj) {
								foreach(PropertyInfo pi in item.GetType().GetProperties(this._flags)) {
									try {
										if(pi.CanRead) {
											string checkPropertyName = propertyName;

											if(propertyName.Contains(".")) {
												checkPropertyName = propertyName.Substring(propertyName.LastIndexOf(".") + 1);
											}

											if(pi.Name == checkPropertyName) {
												object objValue = pi.GetValue(item, null);

												if(objValue.ToString().ToDouble() > accordingObj.ToString().ToDouble()) {
													return true;
												}
											}
										}
									}
									catch {
									}
								}
							}

							return false;
						}
						break;

					case "=":
					case "==":
						if(checkObj is short) {
							return (Int16)checkObj == StringExtensions.ToInt16((string)accordingObj);
						}
						else if(checkObj is int) {
							return (Int32)checkObj == StringExtensions.ToInt32((string)accordingObj);
						}
						else if(checkObj is long) {
							return (Int64)checkObj == StringExtensions.ToInt64((string)accordingObj);
						}
						else if(checkObj is String) {
							return (string)checkObj == (string)accordingObj;
						}
						else if(checkObj is bool) {
							return (bool)checkObj == ((string)accordingObj).IsExpressionTrue();
						}
						else if(checkObj is DateTime) {
							return (DateTime)checkObj == StringExtensions.ToDateTime((string)accordingObj);
						}
						else if(checkObj is double) {
							return (double)checkObj == StringExtensions.ToDouble((string)accordingObj);
						}
						else if(checkObj is decimal) {
							return (decimal)checkObj == StringExtensions.ToDecimal((string)accordingObj);
						}
						else if(checkObj is float) {
							return (float)checkObj == StringExtensions.ToFloat((string)accordingObj);
						}
						else if(checkObj is System.Collections.IList) {
							/*
							 * try to run trough a list of items
							 * */
							foreach(var item in (System.Collections.IList)checkObj) {
								foreach(PropertyInfo pi in item.GetType().GetProperties(this._flags)) {
									try {
										if(pi.CanRead) {
											string checkPropertyName = propertyName;

											if(propertyName.Contains(".")) {
												checkPropertyName = propertyName.Substring(propertyName.LastIndexOf(".") + 1);
											}

											if(pi.Name == checkPropertyName) {
												object objValue = pi.GetValue(item, null);

												if(objValue.ToString() == accordingObj.ToString()) {
													return true;
												}
											}
										}
									}
									catch {
									}
								}
							}

							return false;
						}
						break;

					case ">=":
						if(checkObj is short) {
							return (Int16)checkObj >= StringExtensions.ToInt16((string)accordingObj);
						}
						else if(checkObj is int) {
							return (Int32)checkObj >= StringExtensions.ToInt32((string)accordingObj);
						}
						else if(checkObj is long) {
							return (Int64)checkObj >= StringExtensions.ToInt64((string)accordingObj);
						}
						else if(checkObj is DateTime) {
							return (DateTime)checkObj >= StringExtensions.ToDateTime((string)accordingObj);
						}
						else if(checkObj is double) {
							return (double)checkObj >= StringExtensions.ToDouble((string)accordingObj);
						}
						else if(checkObj is decimal) {
							return (decimal)checkObj >= StringExtensions.ToDecimal((string)accordingObj);
						}
						else if(checkObj is float) {
							return (float)checkObj >= StringExtensions.ToFloat((string)accordingObj);
						}
						else if(checkObj is System.Collections.IList) {
							/*
							 * try to run trough a list of items
							 * */
							foreach(var item in (System.Collections.IList)checkObj) {
								foreach(PropertyInfo pi in item.GetType().GetProperties(this._flags)) {
									try {
										if(pi.CanRead) {
											string checkPropertyName = propertyName;

											if(propertyName.Contains(".")) {
												checkPropertyName = propertyName.Substring(propertyName.LastIndexOf(".") + 1);
											}

											if(pi.Name == checkPropertyName) {
												object objValue = pi.GetValue(item, null);

												if(objValue.ToString().ToDouble() >= accordingObj.ToString().ToDouble()) {
													return true;
												}
											}
										}
									}
									catch {
									}
								}
							}

							return false;
						}
						break;

					case "<=":
						if(checkObj is short) {
							return (Int16)checkObj <= StringExtensions.ToInt16((string)accordingObj);
						}
						else if(checkObj is int) {
							return (Int32)checkObj <= StringExtensions.ToInt32((string)accordingObj);
						}
						else if(checkObj is long) {
							return (Int64)checkObj <= StringExtensions.ToInt64((string)accordingObj);
						}
						else if(checkObj is DateTime) {
							return (DateTime)checkObj <= StringExtensions.ToDateTime((string)accordingObj);
						}
						else if(checkObj is double) {
							return (double)checkObj <= StringExtensions.ToDouble((string)accordingObj);
						}
						else if(checkObj is decimal) {
							return (decimal)checkObj <= StringExtensions.ToDecimal((string)accordingObj);
						}
						else if(checkObj is float) {
							return (float)checkObj <= StringExtensions.ToFloat((string)accordingObj);
						}
						else if(checkObj is System.Collections.IList) {
							/*
							 * try to run trough a list of items
							 * */
							foreach(var item in (System.Collections.IList)checkObj) {
								foreach(PropertyInfo pi in item.GetType().GetProperties(this._flags)) {
									try {
										if(pi.CanRead) {
											string checkPropertyName = propertyName;

											if(propertyName.Contains(".")) {
												checkPropertyName = propertyName.Substring(propertyName.LastIndexOf(".") + 1);
											}

											if(pi.Name == checkPropertyName) {
												object objValue = pi.GetValue(item, null);

												if(objValue.ToString().ToDouble() <= accordingObj.ToString().ToDouble()) {
													return true;
												}
											}
										}
									}
									catch {
									}
								}
							}

							return false;
						}
						break;

					case "!=":
						if(checkObj is short) {
							return (Int16)checkObj != StringExtensions.ToInt16((string)accordingObj);
						}
						else if(checkObj is int) {
							return (Int32)checkObj != StringExtensions.ToInt32((string)accordingObj);
						}
						else if(checkObj is long) {
							return (Int64)checkObj != StringExtensions.ToInt64((string)accordingObj);
						}
						else if(checkObj is string) {
							return (string)checkObj != (string)accordingObj;
						}
						else if(checkObj is bool) {
							return (bool)checkObj != ((string)accordingObj).IsExpressionTrue();
						}
						else if(checkObj is DateTime) {
							return (DateTime)checkObj != StringExtensions.ToDateTime((string)accordingObj);
						}
						else if(checkObj is double) {
							return (double)checkObj != StringExtensions.ToDouble((string)accordingObj);
						}
						else if(checkObj is decimal) {
							return (decimal)checkObj != StringExtensions.ToDecimal((string)accordingObj);
						}
						else if(checkObj is float) {
							return (float)checkObj != StringExtensions.ToFloat((string)accordingObj);
						}
						else if(checkObj is System.Collections.IList) {
							/*
							 * try to run trough a list of items
							 * */
							foreach(var item in (System.Collections.IList)checkObj) {
								foreach(PropertyInfo pi in item.GetType().GetProperties(this._flags)) {
									try {
										if(pi.CanRead) {
											string checkPropertyName = propertyName;

											if(propertyName.Contains(".")) {
												checkPropertyName = propertyName.Substring(propertyName.LastIndexOf(".") + 1);
											}

											if(pi.Name == checkPropertyName) {
												object objValue = pi.GetValue(item, null);

												if(objValue.ToString() != accordingObj.ToString()) {
													return true;
												}
											}
										}
									}
									catch {
									}
								}
							}

							return false;
						}
						break;

					default:
						return false;
				}
			}
			catch(Exception) {
			}

			return false;
		}

		private List<string> SplitQuery(string query) {
			//List<string> list = new List<string>(
			//    query.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
			//);

			List<string> list = new List<string>();
			string tmp = "";
			int count = 0;
			bool canAdd = true;

			foreach(char c in query) {
				if(c != ' ') {
					tmp += c.ToString();
					canAdd = true;
				}
				else if(c == ' ' && tmp.StartsWith("'")) {
					tmp += c.ToString();
					canAdd = false;
				}

				if(c == ' ' || c == '\n' || c == '\r' || c == '\0'
				|| count == (query.Length - 1)) {
					if(canAdd) {
						if(!string.IsNullOrEmpty(tmp)) {
							list.Add(tmp);
							tmp = "";

							canAdd = true;
						}
					}
				}

				count++;
			}

			return list;
		}
	}
}
